#include "PhotonMap.h"
#include "PhongShader.h"
#include <cstdlib>
#include <math.h>
#include <list>



PhotonMap::PhotonMap(SceneStructure& newScene)
{
    _CurrentScene = &newScene;
}

bool PhotonMap::PhotonTrace(Photon &ph, int &bounces, Ray photon_path, Vector3 emit_pos)
{
    photon_path.Advance();
    bool intersect = false;
    Vector3 closest_hit;
    Vector3 hit;
    SurfaceObject * obj = NULL;
    float dist = 100000000;

    for (vector<SurfaceObject*>::iterator itr = _CurrentScene->GetRenderableObjects()->begin(); itr !=  _CurrentScene->GetRenderableObjects()->end(); itr++)
    {
        if ((*itr)->Intersects(photon_path, hit) && (!intersect || (hit - photon_path.start).length() < dist))
        {
            intersect = true;
            closest_hit = hit;
            dist = (hit - photon_path.start).length();
            obj = (*itr);
        }
    }

    if (intersect)
    {
        float ref = obj->_material->GetReflectance();
        float transp = obj->_material->GetTransparancy();
        float diff = 1.0 - ref - transp;
        if (diff < 0.0)
        {
            diff = 0.0;
            transp /= (transp + ref);
            ref /= (transp + ref);
        }
        float abs = diff;
        float total = ref + transp + diff + abs;

        ph._color = ph._color * obj->_material->GetColor() /(1.0 + 0.055*(closest_hit - emit_pos).LengthPow2());
        ph._position = closest_hit;
        ph._incDir = Vector3::normalize(emit_pos - closest_hit);
        float roulett = ((float)rand()) / (float)RAND_MAX;
        roulett *= total;
        if (roulett < diff && bounces > 0 && ph._color.length() > 0.001)
        { // Diffuse reflection
            bounces--;
            Vector3 direction = Vector3::GetRandomNormalizedKernel();
            while (Vector3::dot(obj->GetNormal(closest_hit), direction) < 0.0)
                direction = Vector3::GetRandomNormalizedKernel();

            PhotonTrace(ph, bounces, Ray(direction, closest_hit, 1), closest_hit);
        }
        else  if (roulett < (ref+diff) && bounces > 0 && ph._color.length() > 0.01)
        {
            //Specular reflection
            bounces--;
            ph._color = ph._color *  ref;
            Vector3 refvec = Vector3::normalize(Vector3::reflect(photon_path.direction,obj->GetNormal(closest_hit)));
            PhotonTrace(ph, bounces, Ray(refvec, closest_hit, 1), closest_hit);
        }
        else  if (roulett < (ref+diff+transp) && bounces > 0 && ph._color.length() > 0.01)
        {
            //Refraction
            bounces--;
            ph._color = ph._color * transp;
            PhotonTrace(ph, bounces, obj->Refract(photon_path, closest_hit, obj->_material->_RefractiveIndex), closest_hit);
        }
        else
        {
            //Absorbed
        }
    }
}

void PhotonMap::EmitPhotonsFromLightSource(LightSource light, int amount, vector<SurfaceObject*>& targets, float photonEnergy)
{
    Ray photon_path;
//    int max_bounces = 6;
    int bounces;
    for (int i = 0; i < amount;)
    {
        bounces = 6;
        photon_path = light.GetRandomPhotonPath();
        if (targets.size() != _CurrentScene->GetRenderableObjects()->size())
        {
            float random_target = rand();
            random_target /= RAND_MAX;
            random_target *= targets.size();
            photon_path.direction = targets[(int)random_target]->GetRandomPositionInObject() - light.GetPosition();
        }
        Photon ph(Vector3(0.0, 0.0, 0.0), photonEnergy * Vector3(1.0, 1.0, 1.0));
        if(PhotonTrace(ph, bounces, photon_path, light.GetRandomPositionInLightSource()) && bounces <= 5)
        {
            _photonMap.push_back(ph);
            i++;
            if (i % 1000 == 0)
                cout << i << endl;
        }
    }
}

void PhotonMap::CreatePhotonMap(int TotalAmount, float TotalEnergy, vector<SurfaceObject*>& targets, vector<LightSource*> &lights)
{
    cout << "Creating photon map: " << TotalAmount << endl;

    for (vector<LightSource*>::iterator itr = lights.begin(); itr != lights.end(); itr++)
    {
        EmitPhotonsFromLightSource(*(*itr), (TotalAmount*(*itr)->GetEnergy())/TotalEnergy, targets, (*itr)->GetEnergy());
    }

    _photonMap_ref = new vector<Photon*>;

    for (int i = 0; i < _photonMap.size(); i++)
        _photonMap_ref->push_back(&_photonMap[i]);


    _KDStructredPhotonMap = new KDTree(_photonMap_ref, 0);

}

